/**
 * ***** BEGIN LICENSE BLOCK *****
 * Version: MPL 1.1/GPL 2.0/LGPL 2.1
 *
 * The contents of this file are subject to the Mozilla Public License Version
 * 1.1 (the "License"); you may not use this file except in compliance with
 * the License. You may obtain a copy of the License at
 * http://www.mozilla.org/MPL/
 *
 * Software distributed under the License is distributed on an "AS IS" basis,
 * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License
 * for the specific language governing rights and limitations under the
 * License.
 *
 * The Original Code is Force-TLS.
 *
 * The Initial Developer of the Original Code is
 * Sid Stamm.
 * Portions created by the Initial Developer are Copyright (C) 2009
 * the Initial Developer. All Rights Reserved.
 *
 * Contributor(s):
 *
 * Alternatively, the contents of this file may be used under the terms of
 * either the GNU General Public License Version 2 or later (the "GPL"), or
 * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"),
 * in which case the provisions of the GPL or the LGPL are applicable instead
 * of those above. If you wish to allow use of your version of this file only
 * under the terms of either the GPL or the LGPL, and not to allow others to
 * use your version of this file under the terms of the MPL, indicate your
 * decision by deleting the provisions above and replace them with the notice
 * and other provisions required by the GPL or the LGPL. If you do not delete
 * the provisions above, a recipient may use your version of this file under
 * the terms of any one of the MPL, the GPL or the LGPL.
 *
 * ***** END LICENSE BLOCK *****/
const Cc = Components.classes;
const Ci = Components.interfaces;
const Cu = Components.utils;

Cu.import("resource://gre/modules/NetUtil.jsm");
Cu.import("resource://gre/modules/FileUtils.jsm");

var gForceTLSManager = {
  _type         : "",
  _permissions  : [], /* IForceTLSRecord */
  _bundle       : null,
  _tree         : null,

  _ftlss        : null,

  _view: {
    _rowCount: 0,
    get rowCount() { return this._rowCount; },

    getCellText:
    function (aRow, aColumn) {
      var p = gForceTLSManager._permissions[aRow];
      if(!p) { return; }
      if (aColumn.id == "siteCol")
        return (p.includeSubdomains ? "*." : "") + p.host;
      else if (aColumn.id == "maxageCol") {
        return (('expireType' in p) && (p.expireType == Ci.IForceTLSRecord.EXPIRE_NEVER)
                ? "permanent" : (new Date(p.expireTime)).toString());
      }
      return "";
    },

    isSeparator: function(aIndex) { return false; },
    isSorted: function() { return false; },
    isContainer: function(aIndex) { return false; },
    setTree: function(aTree){},
    getImageSrc: function(aRow, aColumn) {},
    getProgressMode: function(aRow, aColumn) {},
    getCellValue: function(aRow, aColumn) {},
    cycleHeader: function(column) {},
    getRowProperties: function(row){},
    getColumnProperties: function(column){},

    getCellProperties:
    function(row,column){
      if (column.element.getAttribute("id") == "siteCol")
        return "ltr";
      return "";
    }
  },

  get ftlss() {
    if (!this._ftlss) {
      this._ftlss = Cc["@sidstamm.com/forcetls;1"].getService(Ci.IForceTLS);
    }
    return this._ftlss;
  },

  addPermission:
  function () {
    var textbox = document.getElementById("url");
    // trim any leading space and scheme that user may have entered
    var host = textbox.value.replace(/^\s*([-\w]*:\/+)?/, "");
    var includesubd = document.getElementById("allowSubdCheckbox").checked;
    var uri;

    try {
      // canonicalize host name (turn into canonical charset).
      var ioService = Cc["@mozilla.org/network/io-service;1"]
                        .getService(Ci.nsIIOService);
      // we're not going to use the scheme anyway, so scheme can be whatever.
      uri = ioService.newURI("http://"+host, null, null);
      host = uri.asciiHost;
    } catch(ex) {
      var promptService = Cc["@mozilla.org/embedcomp/prompt-service;1"]
                            .getService(Ci.nsIPromptService);
      var message = this._bundle.getString("invalidURI");
      var title = this._bundle.getString("invalidURITitle");
      promptService.alert(window, title, message);
      return;
    }

    // check whether the permission already exists, if not, add it

    // Send EXPIRE_NEVER in case we're using the built-in HSTS and can have
    // permanent records (doesn't work in the custom DB.
    this.ftlss.setStsState(uri, includesubd, 32000000, Ci.IForceTLSRecord.EXPIRE_NEVER);

    // Refresh after delay to wait for db to update
    // need to close in 'this' to preserve context.
    let refreshFcn = function() {
        // reload the list, since it might have replaced something.
        this._loadPermissions();

        // Re-do the sort, since we inserted something who knows where.
        gTreeUtils.sort(this._tree, this._view, this._permissions,
                        this._lastPermissionSortColumn,
                        this._permissionsComparator,
                        this._lastPermissionSortColumn,
                        this._lastPermissionSortAscending);
    };
    window.setTimeout(refreshFcn.bind(this), 100);

    textbox.value = "";
    textbox.focus();

    // covers a case where the site exists already, so the buttons don't disable
    this.onHostInput(textbox);

    // enable "remove all" button as needed
    document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0;
  },

  onHostInput:
  function (aSiteField) {
    document.getElementById("btnForce").disabled = !aSiteField.value;
    document.getElementById("allowSubdCheckbox").disabled = !aSiteField.value;
    if(!aSiteField.value)
      document.getElementById("allowSubdCheckbox").checked = false;
  },

  onHostKeyPress:
  function (aEvent) {
    if (aEvent.keyCode == KeyEvent.DOM_VK_RETURN)
      document.getElementById("btnForce").click();
  },

  onLoad:
  function () {
    this._bundle = document.getElementById("forcetls-strings");
    var params = { forceVisible: true, prefilledHost: "", permissionType: "forcetls"};
    params.windowTitle = this._bundle.getString("prefWindowTitle");
    params.introText = this._bundle.getString("prefWindowIntro");
    this.init(params);
  },

  onImportFile:
  function () {
    var ioService = Cc["@mozilla.org/network/io-service;1"].getService(Ci.nsIIOService);
    let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
    let importFileCallback = function (aResult) {
      if (aResult != Ci.nsIFilePicker.returnCancel && fp.fileURL) {
        var fis = Cc["@mozilla.org/network/file-input-stream;1"]
                    .createInstance(Ci.nsIFileInputStream);
        fis.init(fp.file, -1, -1, Ci.nsIFileInputStream.CLOSE_ON_EOF);
        let inputStream = NetUtil.readInputStreamToString(fis, fis.available(), { charset: "UTF-8" });
        let dataObj = JSON.parse(inputStream);

        for (let host in dataObj) {
          let uri = ioService.newURI("https://"+host, null, null),
              values = dataObj[host],
              includesubd = values.hasOwnProperty("includesubdomains"),
              maxage = 320000,
              expiretype = Ci.IForceTLSRecord.EXPIRE_NEVER;

          if (values.hasOwnProperty("expiretime")) {
            // I know, this is kind of a hack.  We could add the permission
            // manually, but that gets messy.
            let now = new Date().getTime();
            maxage = (values["expiretime"] - now) / 1000;
            expiretype = Ci.IForceTLSRecord.EXPIRE_TIME;
          }
          
          // install it.
          this.ftlss.setStsState(uri, includesubd, maxage, expiretype);
        }

        // refresh UI
        this._loadPermissions();
      }
    };
    fp.init(window, this._bundle.getString("importWindowTitle"),
            Ci.nsIFilePicker.modeOpen);
    fp.appendFilter("JSON files", "*.js; *.json");
    fp.open(importFileCallback.bind(this));
  },

  onExportFile:
  function() {
    let fp = Cc["@mozilla.org/filepicker;1"].createInstance(Ci.nsIFilePicker);
    let exportFileCallback = function (aResult) {
      if (aResult != Ci.nsIFilePicker.returnCancel && fp.fileURL) {
        var fos = Cc["@mozilla.org/network/file-output-stream;1"]
                    .createInstance(Ci.nsIFileOutputStream);
        // flags = write, create, truncate,
        // default permissions
        fos.init(fp.file, 0x02 | 0x08 | 0x20, -1, 0);
        var dataObj = {};
        this._permissions.forEach(function(data) {
          let values = {};
          if (data.includeSubdomains)
            values["includesubdomains"] = true;
          if (data.expireType == Ci.IForceTLSRecord.EXPIRE_TIME)
            values["expiretime"] = data.expireTime;
          dataObj[data.host] = values;
        });
        let dataString = JSON.stringify(dataObj, null, "  ");
        fos.write(dataString, dataString.length);

        // finished
        fos.close();
      }
    };
    fp.init(window, this._bundle.getString("exportWindowTitle"),
            Ci.nsIFilePicker.modeSave);
    fp.appendFilter("JSON files", "*.js; *.json");
    fp.defaultString = "forcetls_export.json";
    fp.open(exportFileCallback.bind(this));
  },

  uninit:
  function (aParams) { },

  init:
  function (aParams) {
    this._type = aParams.permissionType;
    this._manageCapability = aParams.manageCapability;

    var permissionsText = document.getElementById("permissionsText");
    while (permissionsText.hasChildNodes())
      permissionsText.removeChild(permissionsText.firstChild);
    permissionsText.appendChild(document.createTextNode(aParams.introText));

    document.title = aParams.windowTitle;

    if(aParams.forceVisible !== 'undefined') {
      document.getElementById("btnForce").hidden    = !aParams.forceVisible;
      document.getElementById("allowSubdCheckbox").hidden    = !aParams.forceVisible;
    }

    var urlFieldVisible = aParams.forceVisible;

    var urlField = document.getElementById("url");
    urlField.value = aParams.prefilledHost;
    urlField.hidden = !urlFieldVisible;

    this.onHostInput(urlField);

    var urlLabel = document.getElementById("urlLabel");
    urlLabel.hidden = !urlFieldVisible;

    this._loadPermissions();

    urlField.focus();

    this._ltrAtom = Cc["@mozilla.org/atom-service;1"]
                      .getService(Ci.nsIAtomService)
                      .getAtom("ltr");
  },

  onPermissionSelected:
  function () {
    var hasSelection = this._tree.view.selection.count > 0;
    var hasRows = this._tree.view.rowCount > 0;
    document.getElementById("removePermission").disabled = !hasRows || !hasSelection;
    document.getElementById("removeAllPermissions").disabled = !hasRows;
  },

  onPermissionDeleted:
  function () {
    if (!this._view.rowCount)
      return;
    var removedPermissions = [];
    gTreeUtils.deleteSelectedItems(this._tree, this._view,
                                   this._permissions,
                                   removedPermissions);


    var ioService = Cc["@mozilla.org/network/io-service;1"]
                      .getService(Ci.nsIIOService);
    for (var i = 0; i < removedPermissions.length; ++i) {
      dump("FTLSDEBUG: removing permission : " + removedPermissions[i].host + "\n");
      var uri = ioService.newURI("http://"+removedPermissions[i].host, null, null);
      this.ftlss.removeStsState(uri);
    }

    // Re-do the sort, since we inserted this new item at the end.
    gTreeUtils.sort(this._tree, this._view, this._permissions,
                    this._lastPermissionSortColumn,
                    this._permissionsComparator,
                    this._lastPermissionSortColumn,
                    this._lastPermissionSortAscending);

    document.getElementById("removePermission").disabled = !this._permissions.length;
    document.getElementById("removeAllPermissions").disabled = !this._permissions.length;
  },

  onAllPermissionsDeleted:
  function () {
    if (!this._view.rowCount)
      return;
    var removedPermissions = [];
    gTreeUtils.deleteAll(this._tree, this._view, this._permissions, removedPermissions);

    var ioService = Cc["@mozilla.org/network/io-service;1"]
                      .getService(Ci.nsIIOService);
    for (var i = 0; i < removedPermissions.length; ++i) {
      var uri = ioService.newURI("http://"+removedPermissions[i].host, null, null);
      this.ftlss.removeStsState(uri);
    }

    // Re-do the sort, since we inserted this new item at the end.
    gTreeUtils.sort(this._tree, this._view, this._permissions,
                    this._lastPermissionSortColumn,
                    this._permissionsComparator,
                    this._lastPermissionSortColumn,
                    this._lastPermissionSortAscending);

    document.getElementById("removePermission").disabled = true;
    document.getElementById("removeAllPermissions").disabled = true;
  },

  onPermissionKeyPress:
  function (aEvent) {
    if (aEvent.keyCode == 46)
      this.onPermissionDeleted();
  },

  _lastPermissionSortColumn: "",
  _lastPermissionSortAscending: false,
  _permissionsComparator : function (a, b) {
    if ("undefined" == typeof(a)) {
      return -1;
    }
    if ("undefined" == typeof(b)) {
      return 1;
    }
    return a.toLowerCase().localeCompare(b.toLowerCase());
  },

  onPermissionSort:
  function (aColumn) {
      this._lastPermissionSortAscending
        = gTreeUtils.sort(this._tree, this._view, this._permissions, aColumn,
                          this._permissionsComparator,
                          this._lastPermissionSortColumn,
                          this._lastPermissionSortAscending);
    this._lastPermissionSortColumn = aColumn;
  },

  _loadPermissions:
  function () {
    this._tree = document.getElementById("permissionsTree");
    this._tree
    this._permissions = [];

    var enumerator = this.ftlss.enumerator;
    while (enumerator.hasMoreElements()) {
      var nextRecord = enumerator.getNext().QueryInterface(Ci.IForceTLSRecord);
      this._addPermissionToList(nextRecord);
    }

    // sort and display the table
    this._view._rowCount = this._permissions.length;
    this._tree.treeBoxObject.view = this._view;
    this.onPermissionSort("host", false);

    // disable "remove all" button if there are none
    document.getElementById("removeAllPermissions").disabled = this._permissions.length == 0;
  },

  _addPermissionToList:
  function (aFTLSRecord) {
    var data = {'host': aFTLSRecord.host,
                'includeSubdomains': aFTLSRecord.includeSubdomains,
                'expireTime': aFTLSRecord.expireTime,
                'expireType': aFTLSRecord.expireType};
    this._permissions.push(data);
  },

  setHost:
  function (aHost) {
    document.getElementById("url").value = aHost;
  }
};

